package com.sudo.service.admin.service.impl;

import com.sudo.common.constant.MenuConstant;
import com.sudo.common.enums.SysStatus;
import com.sudo.common.constant.UserConstant;
import com.sudo.common.utils.RandomUtil;
import com.sudo.dao.admin.entity.SysButton;
import com.sudo.dao.admin.entity.SysMenu;
import com.sudo.dao.admin.entity.SysRoleButton;
import com.sudo.dao.admin.entity.SysRoleMenu;
import com.sudo.dao.admin.mapper.*;
import com.sudo.dao.admin.model.bo.SysMenuAddBO;
import com.sudo.dao.admin.model.bo.SysMenuUpdateBO;
import com.sudo.dao.admin.model.vo.SysMenuPearVO;
import com.sudo.service.admin.service.AdminBaseService;
import com.sudo.service.admin.service.SysMenuService;
import com.sudo.service.admin.service.SysRedisAuthService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 为道日损 2020-04-25 0:24
 */
@Service
public class SysMenuServiceImpl extends AdminBaseService implements SysMenuService {
    @Resource
    private SysMenuMapper sysMenuMapper;
    @Resource
    private SysMenuMapperCustom sysMenuMapperCustom;
    @Resource
    private SysRoleMenuMapper sysRoleMenuMapper;
    @Resource
    private SysButtonMapper sysButtonMapper;
    @Resource
    private SysRoleButtonMapper sysRoleButtonMapper;
    @Resource
    private SysRedisAuthService sysRedisAuthService;

    /**
     * 根据用户获取有权限的菜单
     * @param mailOrUsername 角色代码
     */
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
    @Override
    public List<SysMenuPearVO> queryMySysMenuByMailOrUsername(String mailOrUsername) {
        List<SysMenu> myMenu = sysMenuMapperCustom.findMyMenuByMailOrUsername(mailOrUsername);
        List<SysMenuPearVO> rootMenus = new ArrayList<>();
        for (SysMenu menu : myMenu) {
            //从最上级菜单开始展示
            if (MenuConstant.MENU_ROOT_CODE.equals(menu.getParentCode())) {
                SysMenuPearVO sysMenuPearVO = new SysMenuPearVO(menu);
                rootMenus.add(sysMenuPearVO);
            }
        }

        for (SysMenuPearVO rootMenu : rootMenus) {
            //把最上级菜单的子菜单获取到，子菜单的子菜单也会获取到(目前是三层，由于是递归调用，100层也没问题)
            List<SysMenuPearVO> child = getChild(rootMenu.getId(), myMenu);
            if (child != null){
                rootMenu.setType(0);
            }
            rootMenu.setChildren(child);
        }
        return rootMenus;
    }

    /**
     *通过传入当前菜单id，获取当前menuCode的子菜单 且子菜单里也有子菜单及其信息，直到子菜单没有子菜单了，递归结束
     * @param menuCode  当前菜单的menuCode
     * @param menus  要查询的菜单范围
     * @return  该id的子菜单
     */
    private List<SysMenuPearVO> getChild(String menuCode,List<SysMenu> menus){
        List<SysMenuPearVO> childList = new ArrayList<>();
        for (SysMenu menu : menus) {
            if(menu.getParentCode().equals(menuCode)){
                //当菜单范围内的父id等于传入的id,获取该id的所有子列表
                SysMenuPearVO sysMenuPearVO = new SysMenuPearVO(menu);
                childList.add(sysMenuPearVO);
            }
        }
        for (SysMenuPearVO menu : childList) {
            //当上面循环语句查询不到子菜单，长度为0，自然也就不会递归调用了
            //将该id的子菜单便利，通过递归调用，给每一个子菜单赋值子菜单
            List<SysMenuPearVO> child = getChild(menu.getId(), menus);
            if (child != null){
                menu.setType(0);
            }
            menu.setChildren(child);
        }
        if(childList.isEmpty()){
            //子菜单的长度为0,返回null,null不会被jackson解析
            return  null;
        }
        return childList;
    }

    /**
     * 获取所有的级联菜单
     */
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
    @Override
    public List<SysMenu> getAllSysMenuList(String status) {
        Example example = new Example(SysMenu.class);
        example.excludeProperties(SysMenu.Field.id);
        Example.Criteria criteria = example.createCriteria();
        criteria.andNotEqualTo(SysMenu.Field.menuCode, UserConstant.SUPER_MENU);
        criteria.andEqualTo(SysMenu.Field.status, status);
        return sysMenuMapper.selectByExample(example);
    }

    /**
     * 通过menuCode获取菜单
     * @param menuCode 菜单code
     * @return 菜单
     */
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
    @Override
    public SysMenu getSysMenuByCode(String menuCode) {
        Example example = new Example(SysMenu.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo(SysMenu.Field.menuCode, menuCode);
        return sysMenuMapper.selectOneByExample(example);
    }

    /**
     * 根据id修改菜单
     * @param sysMenuUpdateBO sysMenuUpdateBO
     * @return int
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    @Override
    public int updateSysMenuByCode(SysMenuUpdateBO sysMenuUpdateBO) {
        //查询原始数据
        SysMenu oldSysMenu = getSysMenuByCode(sysMenuUpdateBO.getMenuCode());

        SysMenu sysMenu = new SysMenu();
        BeanUtils.copyProperties(sysMenuUpdateBO, sysMenu,SysMenu.Field.menuCode);
        if (!SysStatus.ON.name().equals(sysMenu.getStatus())) {
            sysMenu.setStatus(SysStatus.OFF.name());
        }
        Example example = new Example(SysMenu.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo(SysMenu.Field.menuCode, sysMenuUpdateBO.getMenuCode());
        boolean isEqual = oldSysMenu.getStatus().equals(sysMenuUpdateBO.getStatus());
        int affectCount = sysMenuMapper.updateByExampleSelective(sysMenu, example);

        //====== redis ======
        if (affectCount > 0) {
            // 更新 redis_role_permission
            // 更新 redis_menu_button
            if (!isEqual) {
                List<SysRoleMenu> sysRoleMenuList = sysMenuMapperCustom.getRoleMenuByMenuCodeAndRoleStatus(
                        sysMenuUpdateBO.getMenuCode(), SysStatus.ON.name());
                if (SysStatus.ON.name().equals(sysMenuUpdateBO.getStatus())) {
                    //on
                    for (SysRoleMenu rm : sysRoleMenuList) {
                        sysRedisAuthService.onLoadAllRoleMenuButtonAuth(
                                rm.getRoleCode(),
                                rm.getMenuCode()
                        );
                    }
                    sysRedisAuthService.onLoadMenuButtonToRedis(sysMenuUpdateBO.getMenuCode());
                } else {
                    //off
                    for (SysRoleMenu rm : sysRoleMenuList) {
                        sysRedisAuthService.delRedisRolePermissionFromRedis(rm.getRoleCode(), rm.getMenuCode(),null);
                    }
                }
            }
        }
        return affectCount;
    }

    /**
     * 添加菜单，同时需要将菜单权限添加到超级管理员
     * @param sysMenuAddBO 菜单
     * @return int
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    @Override
    public int addSysMenu(SysMenuAddBO sysMenuAddBO) {
        SysMenu sysMenu = new SysMenu();
        BeanUtils.copyProperties(sysMenuAddBO, sysMenu);
        sysMenu.setStatus(sysMenuAddBO.getStatus());

        //添加菜单需要及时给超级管理员添加权限
        SysRoleMenu sysRoleMenu = new SysRoleMenu();
        sysRoleMenu.setMenuCode(sysMenu.getMenuCode());
        sysRoleMenu.setRoleCode(UserConstant.SUPER_ADMIN);
        sysRoleMenuMapper.insertSelective(sysRoleMenu);

        int affectCount = sysMenuMapper.insertSelective(sysMenu);

        //====== redis ======
        if (affectCount > 0 && SysStatus.ON.name().equals(sysMenuAddBO.getStatus())) {
            // redis_role_permission (新增超级管理员的权限)
            sysRedisAuthService.setRedisRolePermissionToRedis(
                    UserConstant.SUPER_ADMIN,
                    sysMenu.getMenuCode(),
                    UserConstant.NONE
            );
        }
        return affectCount;
    }

    /**
     * 根据menuCode删除菜单
     * @param menuCode 菜单代码
     * @return 删除行数
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    @Override
    public int deleteSysMenuByCode(String menuCode) {
        Example example = new Example(SysMenu.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo(SysMenu.Field.menuCode, menuCode);

        //删除菜单下的按钮
        Example example1 = new Example(SysButton.class);
        Example.Criteria criteria1 = example1.createCriteria();
        criteria1.andEqualTo(SysButton.Field.menuCode, menuCode);
        sysButtonMapper.deleteByExample(example1);

        //删除角色-菜单关联关系
        Example example2 = new Example(SysRoleMenu.class);
        Example.Criteria criteria2 = example2.createCriteria();
        criteria2.andEqualTo(SysRoleMenu.Field.menuCode,menuCode);
        sysRoleMenuMapper.deleteByExample(example2);

        //删除角色-菜单-按钮关联关系
        Example example3 = new Example(SysRoleButton.class);
        Example.Criteria criteria3 = example3.createCriteria();
        criteria3.andEqualTo(SysRoleButton.Filed.menuCode,menuCode);
        sysRoleButtonMapper.deleteByExample(example3);
        int affectCount = sysMenuMapper.deleteByExample(example);

        // ====== redis =======
        if (affectCount > 0) {
            // redis_role_permission (删除redis中角色-菜单对应的权限)
            List<SysRoleMenu> sysRoleMenuList = sysMenuMapperCustom.getRoleMenuByMenuCodeAndRoleStatus(
                    menuCode, null);
            for (SysRoleMenu rm : sysRoleMenuList) {
                sysRedisAuthService.delRedisRolePermissionFromRedis(rm.getRoleCode(),menuCode,null);
            }
            // redis_menu_button(删除redis中菜单-按钮权限)
            sysRedisAuthService.delRedisMenuButtonFromRedis(menuCode, null);
        }
        return affectCount;
    }

    /**
     *生成roleCode，严于律己，虽然生成相同的roleCode几率小得可怜，
     * 但墨菲定律告诉我们，只要发生一件事的几率存在，哪怕这种概率小得可怜，
     * 总有一天会发生
     */
    private void setMenuCode(SysMenu sysMenu){
        String menuCode = RandomUtil.getRandomStr(8);
        if (hasMenuCode(menuCode)) {
            setMenuCode(sysMenu);
        }else {
            sysMenu.setMenuCode(menuCode);
        }
    }

    /**
     * 判断数据库中是否已存在相同的 menuCode
     * @param menuCode 菜单Code
     * @return boolean
     */
    private boolean hasMenuCode(String menuCode){
        Example example = new Example(SysMenu.class);
        example.selectProperties(SysMenu.Field.menuCode);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo(SysMenu.Field.menuCode, menuCode);
        List<SysMenu> sysMenuList = sysMenuMapper.selectByExample(example);

        return !CollectionUtils.isEmpty(sysMenuList);
    }

    /**
     * 判断一个菜单是否含有子菜单
     * @param menuCode 菜单Code
     * @return boolean
     */
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
    @Override
    public boolean hasSubSysMenu(String menuCode) {
        Example example = new Example(SysMenu.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo(SysMenu.Field.parentCode, menuCode);
        List<SysMenu> sysMenuList = sysMenuMapper.selectByExample(example);
        return !CollectionUtils.isEmpty(sysMenuList);
    }

    //判断数据库中是否还有该菜单
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
    @Override
    public boolean hasSysMenu(String menuCode) {
        Example example = new Example(SysMenu.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo(SysMenu.Field.menuCode, menuCode);
        List<SysMenu> sysMenuList = sysMenuMapper.selectByExample(example);
        return !CollectionUtils.isEmpty(sysMenuList);
    }

    /**
     * 判断该菜单是否存在子菜单并且被移动
     * @param menuCode 菜单代码
     * @param otherMenuCode 其他菜单代码
     * @return 布尔值
     */
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
    @Override
    public boolean isMoveSysMenu(String menuCode, String otherMenuCode) {
        SysMenu sysMenu = getSysMenuByCode(menuCode);
        if (sysMenu != null) {
            if (sysMenu.getParentCode().equals(otherMenuCode)) {
                return false;
            }
        }
        return true;
    }


}
